package com.flexrrb.containers
{
	import com.flexrrb.containers.cardDeckClasses.Card;
	import com.flexrrb.containers.cardDeckClasses.CardState;
	import com.flexrrb.effects.Scale;
	import com.flexrrb.events.CardEvent;
	
	import flash.display.DisplayObject;
	
	import mx.containers.Canvas;
	import mx.controls.Label;
	import mx.core.UIComponent;
	import mx.effects.AnimateProperty;
	import mx.effects.Effect;
	import mx.effects.Fade;
	import mx.effects.Move;
	import mx.effects.Parallel;
	import mx.effects.Sequence;
	import mx.events.EffectEvent;
	import mx.styles.CSSStyleDeclaration;
	import mx.styles.StyleManager;

	[Event(name="selected", type="com.flexrrb.events.CardEvent")]
	[Event(name="added", type="com.flexrrb.events.CardEvent")]
	[Event(name="removed", type="com.flexrrb.events.CardEvent")]
	[Event(name="removedAll", type="com.flexrrb.events.CardEvent")]
	
	public class CardDeck extends Canvas
	{
		private var _scale:Number;
		
		private var _initialized:Boolean;
		private var _animateBeforeAdding:Boolean;
		private var _isAnimating:Boolean;
		private var _invalidateScale:Boolean;
		
		private var _showTitle:Boolean = true;
		
		private var _gap:Number = 3;
		private var _sideOffset:Number;
		
		private var _currentSelectedIndex:int;
		private var _parallel:Parallel;
		
		private var _start:int;
		private var _end:int;
		private var _totalCards:int;
		
		private var _originalWidth:Number = NaN;
		private var _originalHeigth:Number = NaN;
		private var _tempSelectedIndex:int = -1;
		
		private var _titleLabel:Label;
		private var _cardToBeAdded:Card;
		private var _cardToBeRemoved:Card;
		
		private static var _classConstructed:Boolean = constructClass();
		
		private static function constructClass():Boolean
		{
			var selector:CSSStyleDeclaration = StyleManager.getStyleDeclaration("CardDeck");
			if(!selector)
			{
				selector = new CSSStyleDeclaration();
			}
			
			selector.defaultFactory = function():void
			{
				
			}
			
			StyleManager.setStyleDeclaration("CardDeck", selector, true);
			
			return true;
		}
		
		public function CardDeck()
		{
			super();
			this.horizontalScrollPolicy = this.verticalScrollPolicy = "off";	
		}
		
		public function get isAnimating():Boolean
		{
			return _isAnimating;	
		}
		
		public function set animateBeforeAdding(value:Boolean):void
		{
			_animateBeforeAdding = value;
		}
		
		public function get animateBeforeAdding():Boolean
		{
			return _animateBeforeAdding;
		}
		
		public function get showTitle():Boolean
		{
			return _showTitle;
		}
		
		public function set showTitle(value:Boolean):void
		{
			_showTitle = value;	
		}
		
		public function get gap():Number
		{
			return _gap;
		}
		
		public function set gap(value:Number):void
		{
			_gap = value;
		}
		
		public function get scale():Number
		{
			return _scale;
		}
		
		public function set scale(value:Number):void
		{
			_scale = value;
			_invalidateScale = true;
			invalidateDisplayList();
		}
		
		public function get currentSelectedItem():int
		{
			return _currentSelectedIndex;
		}
		
		public function deselectItem():void
		{
			if(_totalCards == 0)
				return;
				
				minimizeItem();
		}	
		
		public function set selectedIndex(value:int):void
		{
			if(_totalCards == 0)
				return;
			
			if(!_cardToBeAdded && (value < 0 || value >= _totalCards))
				return;
			
			_tempSelectedIndex = value;
			
			if(_tempSelectedIndex == _currentSelectedIndex)
			{
				maximizeItem();
			}
			else if(_tempSelectedIndex < _currentSelectedIndex)
			{
				prevItem();
			}
			else if(_tempSelectedIndex > _currentSelectedIndex)
			{
				nextItem();	
			}
		}
		
		private function maximizeItem():void
		{
			var child:DisplayObject = getChildAt(_currentSelectedIndex);
			(child as Card).originalIndex = getChildIndex(child);
			(child as Card).state = CardState.SELECTING;
			
			super.addChildAt(child, numChildren - 1);
			
			var scale:Scale = new Scale();
			scale.scaleTo = 1;
			_parallel = new Parallel();
			_parallel.addChild(scale);
			
			var effect:Effect = new AnimateProperty();
			(effect as AnimateProperty).property = "x";
			(effect as AnimateProperty).toValue = width * 0.5 - (child.width / this.scale * 0.5)
			_parallel.addChild(effect);
			
			effect = new AnimateProperty();
			(effect as AnimateProperty).property = "y";
			(effect as AnimateProperty).toValue = height * 0.5 - (child.height / this.scale * 0.5)
			_parallel.addChild(effect);
			
			_parallel.target = child;
			_parallel.addEventListener(EffectEvent.EFFECT_END, onMaximizeItemEnd, false, 0, true);
			
			_parallel.play();
		}
		
		private function minimizeItem():void
		{
			var child:Card = getChildAt(numChildren - 1) as Card;
			
			var scale:Scale = new Scale();
			scale.scaleTo = this.scale;
			_parallel = new Parallel();
			_parallel.addChild(scale);
			
			var effect:Effect = new AnimateProperty();
			(effect as AnimateProperty).property = "x";
			(effect as AnimateProperty).toValue = width * 0.5 * (1 - this.scale); //- (child.width * this.scale * 0.5)
			_parallel.addChild(effect);
			
			effect = new AnimateProperty();
			(effect as AnimateProperty).property = "y";
			(effect as AnimateProperty).toValue = height * 0.5 * (1 - this.scale); //- (child.height * this.scale * 0.5)
			_parallel.addChild(effect);
			
			_parallel.target = child;
			_parallel.addEventListener(EffectEvent.EFFECT_END, onMinimizedEnd, false, 0, true);
			_parallel.addEventListener(EffectEvent.EFFECT_START, onAnimateStart, false, 0, true);
			_parallel.play();
		}
		
		private function onAnimateStart(event:EffectEvent):void
		{
			_isAnimating = true;
		}
		
		private function onMinimizedEnd(event:EffectEvent):void
		{
			_isAnimating = false;
			(event.currentTarget as Effect).removeEventListener(EffectEvent.EFFECT_END, onMinimizedEnd);
			var card:Card = (event.target as Effect).target as Card;
			super.addChildAt(card, card.originalIndex);
			card.state = CardState.NORMAL;
		}
		
		private function onMaximizeItemEnd(event:EffectEvent):void
		{
			_isAnimating = false;
			var card:Card = (event.target as Effect).target as Card;
			
			card.width = width;
			card.height = height;
			
			card.state = CardState.SELECTED;
			(event.currentTarget as Effect).removeEventListener(EffectEvent.EFFECT_END, onMaximizeItemEnd);
		}
		
		private function onCardSelect(event:CardEvent):void
		{
			var index:int = getChildIndex(event.currentTarget as DisplayObject);
			
			if(index == _currentSelectedIndex)
				maximizeItem();
			else if(index > _currentSelectedIndex)
				nextItem();
			else
				prevItem();
		}
		
		private function onRemoveAllChildrenEnd(event:EffectEvent):void
		{
			_isAnimating = false;
			
			var i:int = 0;
			var child:DisplayObject;
			
			while(_totalCards > 0)
			{
				child = getChildAt(0);
				if(child != _titleLabel)
				{
					super.removeChild(child);
					_totalCards--;	
				}
				i++;
			}
			
			_currentSelectedIndex = 0;
			_titleLabel.text = "";
			dispatchEvent(new CardEvent(CardEvent.REMOVED_ALL));
		}
		
		override public function removeAllChildren():void
		{
			if(_totalCards == 0)
			{
				return;
			}
			
			_start = Math.max(_currentSelectedIndex - 1, 0);
			_end = Math.min(_currentSelectedIndex + 1, _totalCards - 1);
			
			_parallel = new Parallel();
			
			for(var i:int = _start; i <= _end; i++)
			{
				var fade:Fade = new Fade(getChildAt(i));
				fade.alphaTo = 0;
				_parallel.addChild(fade);
			}
			
			_parallel.addEventListener(EffectEvent.EFFECT_END, onRemoveAllChildrenEnd, false, 0, true);
			_parallel.play();
		}
		
		override public function removeChild(child:DisplayObject):DisplayObject
		{
			if(child == null)
				return null;
			
			if((child as Card).state == CardState.SELECTING || (child as Card).state == CardState.SELECTED)
			{
				super.removeChild(child);
				return child;
			}
			
			_cardToBeRemoved = child as Card;
			commitProperties();
			
			return _cardToBeRemoved;
		}
		
		override public function addChildAt(child:DisplayObject, index:int):DisplayObject
		{
			var card:Card;
			if(!(child is Card))
			{
				card = new Card();
				card.title = "Card " + (index + 1);
				(child as UIComponent).setStyle("verticalCenter", 0);
				(child as UIComponent).setStyle("horizontalCenter", 0);
							
				card.addChild(child);
			}
			
			if(!card.hasEventListener(CardEvent.SELECTED))
				card.addEventListener(CardEvent.SELECTED, onCardSelect, false, 0, true);
			
			//adding a new card after initialization
			if(initialized)
			{
				card.width = width;
				card.height = height;
				card.scaleX = card.scaleY = scale;
				card.visible = false;
					
				//first check to see if index is larger than _totalCards, if yes, choose _totalCards
				//then check to see if the result is smaller than 0, if yes, choose 0
				//this helps to clamp the value between 0 - _totalCards
				index = Math.max(Math.min(index, _totalCards), 0);
				card.originalIndex = index;
				card.title = "Card " + (index + 1);
				
				_cardToBeAdded = card; 
				invalidateProperties();
				return _cardToBeAdded;
			}
			_totalCards++;
			
			return super.addChildAt(card, index);
		}
		
		override protected function measure():void
		{
			super.measure();
		}
		
		override protected function commitProperties():void
		{
			super.commitProperties();
			
			if(_titleLabel && _totalCards > 0)
			{
				//var card:Card = getChildAt(0) as Card;
				var card:Card = getChildAt(_currentSelectedIndex) as Card;
				_titleLabel.text = card.title;
			}
			
			if(_cardToBeAdded)
			{
				if(_totalCards == 0)
					_sideOffset = (width - ((width * 0.5 - width * scale * 0.5) + (width * scale))) * 0.5; 
				
				_cardToBeAdded.y = height * 0.5 - height * scale * 0.5;
				if(_currentSelectedIndex == _cardToBeAdded.originalIndex)
				{
					showCard();	
				}
				else
				{
					selectedIndex = _cardToBeAdded.originalIndex;
				}
			}
			else if(_cardToBeRemoved)
			{
				var cardIndex:int = getChildIndex(_cardToBeRemoved);
				if(cardIndex == _currentSelectedIndex)
				{
					removeCard();
				}
				else
				{
					selectedIndex = cardIndex; 
				}
			}
		}
		
		override protected function createChildren():void
		{
			super.createChildren();
			
			if(showTitle)
			{
				_titleLabel = new Label();
				_titleLabel.setStyle("textAlign", "center");
				_titleLabel.setStyle("horizontalCenter", 0);
				_titleLabel.visible = false;
				super.addChildAt(_titleLabel, numChildren);
			}
		}
		
		override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
		{
			super.updateDisplayList(unscaledWidth, unscaledHeight);
			var child:DisplayObject;
			
			if(!_initialized)
			{
				_titleLabel.width = 100;
				_titleLabel.height = 30;
					
				if(_totalCards > 0)
				{
					var i:int;
					
					for(i = 0; i < _totalCards; i++)
					{
						child = getChildAt(i);
						child.width = unscaledWidth;
						child.height = unscaledHeight;
						child.scaleX = child.scaleY = scale;
						(child as UIComponent).validateNow();
						
						child.y = unscaledHeight * 0.5 - child.height * 0.5;
					}
					
					invalidateItems();
				}
					
				_initialized = true;
				_invalidateScale = false;
			}
			
			if(_invalidateScale)
			{
				rescaleItems();
				_invalidateScale = false;
			}
			
			if(_titleLabel && (!_titleLabel.visible) && _totalCards > 0)
			{
				child = getChildAt(0);
				_titleLabel.y = child.y * 0.5 - _titleLabel.height * 0.5;
				_titleLabel.visible = true;
			}
			
			if((!isNaN(_originalHeigth)) && (!isNaN(_originalWidth)))
			{
				//the size of the deck have changed, so we must reposition the cards
				if(_originalHeigth != unscaledHeight || _originalWidth != unscaledWidth)
				{
					for(i = 0; i < _totalCards; i++)
					{
						child = getChildAt(i);
						child.width = unscaledWidth * child.scaleX;
						child.height = unscaledHeight * child.scaleY;
						(child as UIComponent).validateNow();
						
						child.y = unscaledHeight * 0.5 - child.height * 0.5;
					}
					
					repositionItems();
				}
			}
			
			_originalWidth = unscaledWidth;
			_originalHeigth = unscaledHeight;
		}
		
		private function onSlidePrevEnd(event:EffectEvent):void
		{
			_isAnimating = false;
			(event.currentTarget as Effect).removeEventListener(EffectEvent.EFFECT_END, onSlidePrevEnd);
			_currentSelectedIndex--;
			
			var child:Card = getChildAt(_currentSelectedIndex) as Card;
			if(_titleLabel)
			{
				_titleLabel.text = (child as Card).title;
			}
			
			if(_tempSelectedIndex > -1)
			{
				if(_currentSelectedIndex > _tempSelectedIndex)
				{
					prevItem();
				}
				else
				{
					_tempSelectedIndex = -1;
					if(!_cardToBeAdded && !_cardToBeRemoved)
					{
						maximizeItem();	
					}
					else if(_cardToBeAdded)
					{
						showCard();
					}
					else if(_cardToBeRemoved)
					{
						removeCard();
					}
				}
			}
			
		}
		
		private function onSlideNextEnd(event:EffectEvent):void
		{
			_isAnimating = false;
			(event.currentTarget as Effect).removeEventListener(EffectEvent.EFFECT_END, onSlideNextEnd);
			_currentSelectedIndex++;
			
			var child:Card
			if(!_cardToBeAdded)
			{
				child = getChildAt(_currentSelectedIndex) as Card;
			}
			else
			{
				child = _cardToBeAdded;
			}
						
			if(_titleLabel)
			{
				_titleLabel.text = (child as Card).title;
			}
			
			if(_tempSelectedIndex > -1)
			{
				if(_currentSelectedIndex < _tempSelectedIndex)
					nextItem();
				else
				{
					_tempSelectedIndex = -1;
					if(!_cardToBeAdded)
					{
						maximizeItem();
					}
					else
					{
						showCard();
					}
				}
			}
		}
		
		private function rescaleItems():void
		{
			var child:DisplayObject = getChildAt(_currentSelectedIndex);
			var childWidth:Number = width * scale;
			var newX:Number = width * 0.5 * (1 - scale);
			var grow:Scale;
			var move:Move;
			
			_sideOffset = width * 0.5 - childWidth * 0.5;
			if(_sideOffset <= (width * scale * 0.5))
			{
				_sideOffset *= 0.5;
			}
			
			_parallel = new Parallel();
			
			for(var i:int = 0; i < _totalCards; i++)
			{	
				child = getChildAt(i);
				grow = new Scale(child);
				grow.scaleTo = scale;
				_parallel.addChild(grow);
				
				move = new Move(child);
				if(child == getChildAt(_currentSelectedIndex))
				{
					move.xTo = newX;
				}
				else if(child.x + child.width > 0 && child.x + child.width < width * 0.5)
				{
					move.xTo = -(childWidth) + _sideOffset;
				}
				else if(child.x + child.width < width * 0.5)
				{
					move.xTo = -(childWidth + _sideOffset);
				}
				else if(child.x > width * 0.5 && child.x < width)
				{
					move.xTo = width - _sideOffset;
				}
				else
				{
					move.xTo = width + childWidth + _sideOffset;
				}
				
				move.yTo = height * 0.5 * (1 - scale); 
				_parallel.addChild(move);
			}
			
			move = new Move(_titleLabel);
			move.yTo = (height * 0.5 * ( 1 - scale) * 0.5) - _titleLabel.height * 0.5;
			
			_parallel.addChild(move);
			_parallel.addEventListener(EffectEvent.EFFECT_END, onRescaleEnd, false, 0, true);
			_parallel.play();
		}
		
		private function onRescaleEnd(event:EffectEvent):void
		{
			(event.currentTarget as Effect).removeEventListener(EffectEvent.EFFECT_END, onRescaleEnd);
			var child:DisplayObject = getChildAt(_currentSelectedIndex);
			trace("child.width: " + child.width);
		}
		
		private function removeCard():void
		{
			var sequence:Sequence = new Sequence();
			var fade:Fade = new Fade(_cardToBeRemoved);
			fade.alphaTo = 0;
			
			sequence.addChild(fade);
			
			if(_totalCards > 0)
			{
				var start:int;
				var end:int;
				
				var cardIndex:int = getChildIndex(_cardToBeRemoved);
				
				if(cardIndex != _totalCards - 1)
				{
					start = _currentSelectedIndex + 1 > _totalCards ? _currentSelectedIndex : _currentSelectedIndex + 1;
					end = start + 1 >= _totalCards ? start : start + 1;
					nextItem(start, end, false);
				}
				else
				{
					start = _currentSelectedIndex - 2 < 0 ? 0 : _currentSelectedIndex - 2;
					end = start == 0 ? 0 : _currentSelectedIndex - 1;
					prevItem(start, end, false);
				}
				
				sequence.addChild(_parallel);	 
			}
			
			sequence.addEventListener(EffectEvent.EFFECT_END, onRemoveCompleted, false, 0, true);
			sequence.play();
		}
		
		private function showCard():void
		{
			var index:int = _cardToBeAdded.originalIndex ;
			_cardToBeAdded.x = width * 0.5 - width * scale * 0.5;
			_cardToBeAdded.alpha = 0;
			_cardToBeAdded.visible = true;
			super.addChildAt(_cardToBeAdded, _cardToBeAdded.originalIndex);
			
			var sequence:Sequence = new Sequence();
			
			if(_totalCards > 0 && _currentSelectedIndex < _totalCards)
			{
				var start:int = _currentSelectedIndex + 1 > _totalCards ? _currentSelectedIndex : _currentSelectedIndex + 1;
				var end:int = start + 1 > _totalCards ? start : start + 1;
				
				prevItem(start, end, false);
				sequence.addChild(_parallel);
			}
			
			var fade:Fade = new Fade(_cardToBeAdded);
			fade.alphaTo = 1;
			
			sequence.addChild(fade);
			sequence.addEventListener(EffectEvent.EFFECT_END, onAddCardCompleted, false, 0, true);
			sequence.play();
		}
		
		private function onRemoveCompleted(event:EffectEvent):void
		{
			_isAnimating = false;
			(event.currentTarget as Effect).removeEventListener(EffectEvent.EFFECT_END, onRemoveCompleted);
			dispatchEvent(new CardEvent(CardEvent.REMOVED, _cardToBeRemoved));
			_totalCards--;
			
			if(_currentSelectedIndex >= _totalCards && _totalCards > 0)
				_currentSelectedIndex = _totalCards - 1;
			
			super.removeChild(_cardToBeRemoved);
			_cardToBeRemoved = null;
		}
		
		private function onAddCardCompleted(event:EffectEvent):void
		{
			(event.currentTarget as Effect).removeEventListener(EffectEvent.EFFECT_END, onAddCardCompleted);
			dispatchEvent(new CardEvent(CardEvent.ADDED, _cardToBeAdded));
			_currentSelectedIndex = _cardToBeAdded.originalIndex;
			_totalCards++;
			_cardToBeAdded = null;
		}
		
		private function nextItem(start:int=-1, end:int=-1, autoPlay:Boolean=true):void
		{
			var effect:AnimateProperty;
			var child:DisplayObject;
			
			if(start != -1)
			{
				_start = start;
			}
			else
			{
				_start = _currentSelectedIndex == 0 ? _currentSelectedIndex : _currentSelectedIndex - 1;
			}
			
			if(end != -1)
			{
				_end = end;
			}
			else
			{
				_end = _currentSelectedIndex + 2 >= _totalCards ? _totalCards - 1 : _currentSelectedIndex + 2;
			}
			
			_parallel = new Parallel();
			
			for(var i:int = _start; i <= _end; i++)
			{
				child = getChildAt(i);
				child.visible = true;
				effect = new AnimateProperty(child);
				effect.property = "x";
				effect.toValue = child.x - (_sideOffset + child.width);
				_parallel.addChild(effect);				
			}
			
			if(autoPlay)
			{
				_parallel.addEventListener(EffectEvent.EFFECT_END, onSlideNextEnd, false, 0, true);
				_parallel.play();
			}
		}
		
		private function prevItem(start:int=-1, end:int=-1, autoPlay:Boolean=true):void
		{
			var effect:AnimateProperty;
			var child:DisplayObject;
			
			if(start != -1)
			{
				_start = start;
			}
			else
			{
				_start = _currentSelectedIndex - 2 < 0 ? 0 : _currentSelectedIndex - 2;
			}
			
			if(end != -1)
			{
				_end = end;
			}
			else
			{
				_end = _currentSelectedIndex + 1 >= _totalCards ? _totalCards - 1 : _currentSelectedIndex + 1;
			}
			
			_parallel = new Parallel();
			for(var i:int = _start; i <= _end; i++)
			{
				child = getChildAt(i);
				
				child.visible = true;
				effect = new AnimateProperty(child);
				effect.property = "x";
				effect.toValue = child.x + (_sideOffset + child.width);
				_parallel.addChild(effect);
			}
			
			if(autoPlay)
			{
				_parallel.addEventListener(EffectEvent.EFFECT_END, onSlidePrevEnd, false, 0, true);
				_parallel.play();
			}
		}
		
		private function repositionItems():void
		{
			var child:DisplayObject = getChildAt(_currentSelectedIndex);
			_sideOffset = width * 0.5 - child.width * 0.5;
			if(_sideOffset <= child.width * 0.5)
			{
				_sideOffset = _sideOffset * 0.5;
			}
			
			child.x = width * 0.5 - child.width * 0.5
			
			var start:int = _currentSelectedIndex == 0 ? 0 : _currentSelectedIndex - 1;
			var end:int = _currentSelectedIndex + 1 == _totalCards ? _totalCards - 1 : _currentSelectedIndex + 1;
			
			for(var i:int = 0; i < _totalCards; i++)
			{
				child = getChildAt(i);
				
				if(i == _currentSelectedIndex)
					continue;
				
				if(i < start)
				{
					child.x = -(2 * child.width);
				}	
				else if(i == start)
				{
					child.x = -(child.width) + _sideOffset;
				}
				else if(i > end)
				{
					child.x = width + child.width;
				}
				else if(i == end)
				{
					child.x = width - _sideOffset;
				}
			}
		}
		
		private function invalidateItems():void
		{
			var child:UIComponent;
			var childWidth:Number;
			
			child = getChildAt(0) as UIComponent;
			childWidth = child.width;
			
			/*
			2. calculate how to layout the child, the algorithm is as follow
			   
			   a. place the first item at the center of the deck. Check to see if there are enough space
			   to accomodate cards on both sides by half their length.
			   
			   b. if not, take the half of the space on one side as the offset for card placement
			*/
			
			_sideOffset = width * 0.5 - childWidth * 0.5;
			if(_sideOffset <= childWidth * 0.5)
			{
				_sideOffset = _sideOffset * 0.5;
			}
			
			//position the first card in the middle
			child.x = width * 0.5 - child.width * 0.5;
			child.visible = true;
			//check to see if there is another card to be placed at the right hand side
			if(_totalCards >= 2)
			{
				child = getChildAt(1) as UIComponent;
				child.x = width - _sideOffset;
				child.visible = true;
				
				//place all the child at a far location
				for (var i:int = 2; i < _totalCards; i++)
				{
					child = getChildAt(i) as UIComponent;
					child.visible = false;
					child.x = width + child.width;	
				}
				
			}
			
			_currentSelectedIndex = 0;
		}
	}
}